// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

#include "stdafx.h"
#include "UniformVar.h"
#include "ShaderProgram.h"
#include "ShaderEvent.h"

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

UniformVarParams::UniformVarParams(GLenum type) 
: type(type)
{
}

UniformVarParams::UniformVarParams(GLenum type, GLint i) 
: type(type)
{
    value.float_ = (GLfloat)i;
}

UniformVarParams::UniformVarParams(GLfloat v) 
: type(GL_FLOAT)
{
    value.float_ = v;
}

UniformVarParams::UniformVarParams(GLfloat a, GLfloat b, GLfloat c) 
: type(GL_FLOAT_VEC3)
{
    value.float3[0] = a;
    value.float3[1] = b;
    value.float3[2] = c;
}

UniformVarParams::UniformVarParams(GLfloat a, GLfloat b, GLfloat c, GLfloat d) 
: type(GL_FLOAT_VEC4)
{
    value.float4[0] = a;
    value.float4[1] = b;
    value.float4[2] = c;
    value.float4[3] = d;
}

UniformVarParams::UniformVarParams(GLfloat m[4][4])
: type(GL_FLOAT_MAT4)
{
    std::memcpy(&m[0], &value.floatMat4[0], sizeof(GLfloat) * 16);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

UniformVar::UniformVar(const UniformVarParams& params)
: params(params), location(-1), program(NULL)
{
}

GLenum UniformVar::getType() const
{
    ASSERT(program != NULL);
    ASSERT(!name.empty());
    ASSERT(location != -1);

    return params.type;
}

void UniformVar::setProgram(ShaderProgram* program)
{
    this->program = program;
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

void UniformVar::setValue(const UniformVarParams& params)
{
    this->params = params;
    writeToRenderContext();
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

void UniformVar::writeToRenderContext()
{
    ASSERT(program != NULL);
    ASSERT(!name.empty());
    ASSERT(location != -1);

    // The shader program must be current or else setting the 
    // variable will fail
    program->activate();    

    queryLocation();

    if(params.type == GL_FLOAT)
    {
        glUniform1f(location, params.value.float_);
    }
    else if(params.type == GL_SAMPLER_2D)
    {
        glUniform1i(location, (GLuint)params.value.float_);
    }
    else if(params.type == GL_FLOAT_VEC3)
    {
        glUniform3fv(location, 1, params.value.float3);
    }
    else if(params.type == GL_FLOAT_VEC4)
    {
        glUniform4fv(location, 1, params.value.float4);
    }
    else if(params.type == GL_FLOAT_MAT4)
    {
        glUniformMatrix4fv(location, 1, false, params.value.floatMat4[0]);
    }
    else
    {
        ASSERT(false);
    }
    CHECK_GL_ERRORS();

    // Leave things as they were
    program->deactivate();    
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

void UniformVar::setName(const std::wstring& newName)
{
    Selectable::setName(newName);
    CT2A ansi(newName.c_str());
    name = ansi;

}

void UniformVar::setAnsiName(const std::string& newName)
{
    name = newName;
    CA2T unicode(name.c_str());
    Selectable::setName((wchar_t*)unicode);
}

const char* UniformVar::getAnsiName() const 
{ 
    return name.c_str(); 
}

 // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

void UniformVar::readFromRenderContext()
{
    queryLocation();

    ASSERT(program != NULL);
    ASSERT(!getName().empty());
    ASSERT(location != -1);

    switch(params.type)
    {
    case GL_FLOAT:
        glGetUniformfv(program->getId(), location, &params.value.float_);
        break;
    case GL_FLOAT_VEC3:
        glGetUniformfv(program->getId(), location, params.value.float3);
        break;
    case GL_FLOAT_VEC4:
        glGetUniformfv(program->getId(), location, params.value.float4);
        break;
    case GL_FLOAT_MAT4:
        glGetUniformfv(program->getId(), location, params.value.floatMat4[0]);
        break;
    case GL_SAMPLER_2D:
    {
        GLint aux;
        glGetUniformiv(program->getId(), location, &aux);
        params.value.float_ = (GLfloat)aux;
        break;
    }
    default:
        dbg("Unknown type", typeToTypeName(params.type));
        ASSERT(false);
    }
    CHECK_GL_ERRORS();
}

void UniformVar::queryLocation() const
{
    if(location != -1)
    {
        return;
    }
    location = glGetUniformLocation(program->getId(), &name[0]);
    CHECK_GL_ERRORS();
}



size_t UniformVar::getNumFloats() const
{
    switch(params.type)
    {
    case GL_FLOAT:
    case GL_SAMPLER_2D:
        return 1;
    case GL_FLOAT_VEC3:
        return 3;
    case GL_FLOAT_VEC4:
        return 4;
    case GL_FLOAT_MAT4:
        return 16;
    default:
        return 0;
    }
}

const GLfloat* UniformVar::getFloats() const
{
    return &params.value.float_;
}

void UniformVar::setFloat(size_t i, float newVal)
{
    ASSERT(i < getNumFloats());
    (&params.value.float_)[i] = newVal;
    writeToRenderContext();
    CHECK_GL_ERRORS();

    if(program != NULL)
    {
        ShaderEvent event_(ShaderEvent::UniformVariableChanged);
        event_.uniVar = this;
        program->notifyEvent(event_);
    }
}


std::ostream& operator<<(std::ostream& os, const UniformVar& var)
{
    var.queryLocation();

    switch(var.params.type)
    {

    case GL_FLOAT:
        
        return os << var.params.value.float_;
    
    case GL_FLOAT_VEC3:
    
        for(int i = 0; i < 3; ++i)
        {
            os << var.params.value.float3[i] << (i == 2 ? "" : ", ");
        }
        return os;
    
    case GL_FLOAT_VEC4:
        
        for(int i = 0; i < 4; ++i)
        {
            os << var.params.value.float4[i] << (i == 3 ? "" : ", ");
        }
        return os;
    
    case GL_FLOAT_MAT4:
        
        for(int i = 0; i < 4; ++i)
        {
            for(int j = 0; j < 4; ++j)
            {
                os << var.params.value.floatMat4[i][j] << (j == 3 ? "" : ", ");
            }
            os << (i == 3 ? "" : "\n");
        }
        return os;

    default:
        ASSERT(false);
        return os;
    }
}


#define MPG_ADD_UNIF_VAR(type) case type: return #type

std::string UniformVar::typeToTypeName(GLenum type)
{
    switch(type)
    {
        MPG_ADD_UNIF_VAR(GL_FLOAT);
        MPG_ADD_UNIF_VAR(GL_FLOAT_VEC2); 
        MPG_ADD_UNIF_VAR(GL_FLOAT_VEC3);
        MPG_ADD_UNIF_VAR(GL_FLOAT_VEC4); 
        MPG_ADD_UNIF_VAR(GL_INT); 
        MPG_ADD_UNIF_VAR(GL_INT_VEC2);
        MPG_ADD_UNIF_VAR(GL_INT_VEC3); 
        MPG_ADD_UNIF_VAR(GL_INT_VEC4);
        MPG_ADD_UNIF_VAR(GL_BOOL); 
        MPG_ADD_UNIF_VAR(GL_BOOL_VEC2); 
        MPG_ADD_UNIF_VAR(GL_BOOL_VEC3); 
        MPG_ADD_UNIF_VAR(GL_BOOL_VEC4); 
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT2);
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT3);
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT4); 
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT2x3); 
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT2x4); 
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT3x2);
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT3x4); 
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT4x2); 
        MPG_ADD_UNIF_VAR(GL_FLOAT_MAT4x3); 
        MPG_ADD_UNIF_VAR(GL_SAMPLER_1D); 
        MPG_ADD_UNIF_VAR(GL_SAMPLER_2D); 
        MPG_ADD_UNIF_VAR(GL_SAMPLER_3D); 
        MPG_ADD_UNIF_VAR(GL_SAMPLER_CUBE); 
        MPG_ADD_UNIF_VAR(GL_SAMPLER_1D_SHADOW); 
        MPG_ADD_UNIF_VAR(GL_SAMPLER_2D_SHADOW);
    default:
        ASSERT(0);
        return "";
    }
}
